package hawk.example.bookstore.sql.provider;

import static glz.hawkframework.mybatis.writer.MybatisBuilderContextImpl.*;
import static glz.hawkframework.sql.support.impl.DSL.*;
import static glz.hawkframework.support.ArgumentSupport.*;
import static glz.hawkframework.support.LogicSupport.*;
import static hawk.example.bookstore.support.AuthorSupport.*;

import glz.hawkframework.helper.ObjectHelper;
import glz.hawkframework.mybatis.sql.MybatisParam;
import glz.hawkframework.mybatis.sql.provider.AbstractSqlProvider;
import glz.hawkframework.mybatis.statement.GeneralStatementProvider;
import glz.hawkframework.mybatis.statement.InsertStatementProvider;
import glz.hawkframework.mybatis.statement.MultiRowsInsertStatementProvider;
import glz.hawkframework.mybatis.statement.UpdateStatementProvider;
import glz.hawkframework.mybatis.statement.impl.GeneralStatementProviderImpl;
import glz.hawkframework.mybatis.statement.impl.InsertStatementProviderImpl;
import glz.hawkframework.mybatis.statement.impl.MultiRowsInsertStatementProviderImpl;
import glz.hawkframework.mybatis.statement.impl.UpdateStatementProviderImpl;
import glz.hawkframework.mybatis.writer.MybatisBuilderContext;
import glz.hawkframework.sql.condition.Condition;
import glz.hawkframework.sql.dsl.delete.Delete;
import glz.hawkframework.sql.dsl.insert.Insert;
import glz.hawkframework.sql.dsl.select.Select;
import glz.hawkframework.sql.dsl.update.Update;
import glz.hawkframework.sql.support.SqlBuilder;
import glz.hawkframework.sql.support.impl.DefaultAliasedNamedColumn;
import glz.hawkframework.sql.util.QueryWrapper;

import hawk.example.bookstore.po.AuthorPo;
import hawk.example.bookstore.update.AuthorColumnUpdate;
import hawk.example.bookstore.update.AuthorUpdate;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.ibatis.builder.annotation.ProviderMethodResolver;
import org.springframework.stereotype.Component;

@Component
public class AuthorSqlProvider extends AbstractSqlProvider implements ProviderMethodResolver {
    public AuthorSqlProvider(SqlBuilder<MybatisBuilderContext> sqlBuilder) {
        super(sqlBuilder);
    }

    protected Insert buildInsert() {
        return insertInto(AUTHOR, COLUMNS).values(Arrays.stream(PARAM_COLUMNS).map(p->p.prefix(InsertStatementProvider.PARAM_PREFIX)).toArray()).build();
    }

    public InsertStatementProvider<AuthorPo> insert(AuthorPo authorPo) {
        String key = buildKey("insert", AuthorPo.class);
        String insertSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildInsert(), DUMMY_INSTANCE));
        return InsertStatementProviderImpl.builder(insertSql, authorPo).build();
    }

    protected Insert buildInsertSelective(AuthorPo authorPo) {
        List<DefaultAliasedNamedColumn> columns = new ArrayList<>();
        List<MybatisParam> params = new ArrayList<>();
        if (authorPo.getAuthorId() != null) {columns.add(AUTHOR_ID); params.add(PARAM_AUTHOR_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getAuthorName() != null) {columns.add(AUTHOR_NAME); params.add(PARAM_AUTHOR_NAME.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getNationality() != null) {columns.add(NATIONALITY); params.add(PARAM_NATIONALITY.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getContact() != null) {columns.add(CONTACT); params.add(PARAM_CONTACT.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getCreateDateTime() != null) {columns.add(CREATE_DATE_TIME); params.add(PARAM_CREATE_DATE_TIME.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getUpdateDateTime() != null) {columns.add(UPDATE_DATE_TIME); params.add(PARAM_UPDATE_DATE_TIME.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getDeleteDateTime() != null) {columns.add(DELETE_DATE_TIME); params.add(PARAM_DELETE_DATE_TIME.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getCreateUserId() != null) {columns.add(CREATE_USER_ID); params.add(PARAM_CREATE_USER_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getUpdateUserId() != null) {columns.add(UPDATE_USER_ID); params.add(PARAM_UPDATE_USER_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getDeleteUserId() != null) {columns.add(DELETE_USER_ID); params.add(PARAM_DELETE_USER_ID.prefix(InsertStatementProvider.PARAM_PREFIX));}
        if (authorPo.getRecordVersion() != null) {columns.add(RECORD_VERSION); params.add(PARAM_RECORD_VERSION.prefix(InsertStatementProvider.PARAM_PREFIX));}
        return insertInto(AUTHOR, columns.toArray(new DefaultAliasedNamedColumn[0])).values(params.toArray(new MybatisParam[0])).build();
    }

    public InsertStatementProvider<AuthorPo> insertSelective(AuthorPo authorPo) {
        String insertSql = sqlBuilder.build(buildInsertSelective(authorPo), DUMMY_INSTANCE);
        return InsertStatementProviderImpl.builder(insertSql, authorPo).build();
    }

    protected Insert buildInsertMultiple(List<AuthorPo> authorPos) {
        MybatisParam[][] paramses = new MybatisParam[authorPos.size()][11];
        for (int i = 0; i < authorPos.size(); i++) {
            MybatisParam[] params = new MybatisParam[11];
            params[0] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_AUTHOR_ID.getParamName()) , PARAM_AUTHOR_ID.getJdbcType()).build();
            params[1] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_AUTHOR_NAME.getParamName()) , PARAM_AUTHOR_NAME.getJdbcType()).build();
            params[2] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_NATIONALITY.getParamName()) , PARAM_NATIONALITY.getJdbcType()).build();
            params[3] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_CONTACT.getParamName()) , PARAM_CONTACT.getJdbcType()).build();
            params[4] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_CREATE_DATE_TIME.getParamName()) , PARAM_CREATE_DATE_TIME.getJdbcType()).build();
            params[5] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_UPDATE_DATE_TIME.getParamName()) , PARAM_UPDATE_DATE_TIME.getJdbcType()).build();
            params[6] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_DELETE_DATE_TIME.getParamName()) , PARAM_DELETE_DATE_TIME.getJdbcType()).build();
            params[7] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_CREATE_USER_ID.getParamName()) , PARAM_CREATE_USER_ID.getJdbcType()).build();
            params[8] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_UPDATE_USER_ID.getParamName()) , PARAM_UPDATE_USER_ID.getJdbcType()).build();
            params[9] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_DELETE_USER_ID.getParamName()) , PARAM_DELETE_USER_ID.getJdbcType()).build();
            params[10] = MybatisParam.builder(String.format("%s[%d].%s", MultiRowsInsertStatementProvider.PARAM_PREFIX, i, PARAM_RECORD_VERSION.getParamName()) , PARAM_RECORD_VERSION.getJdbcType()).build();
            paramses[i] = params;
        }
        return insertInto(AUTHOR, COLUMNS).valueses(paramses).build();
    }

    public MultiRowsInsertStatementProvider<AuthorPo> insertMultiple(List<AuthorPo> authorPos) {
        String insertSql = sqlBuilder.build(buildInsertMultiple(authorPos), DUMMY_INSTANCE);
        return MultiRowsInsertStatementProviderImpl.builder(insertSql, authorPos).build();
    }

    public static Delete buildDeleteByPrimaryKey() {
        return deleteFrom(AUTHOR).where(AUTHOR_ID.eq(PARAM_AUTHOR_ID.prefix(GeneralStatementProvider.PARAM_PREFIX)), and(RECORD_VERSION.eq(PARAM_RECORD_VERSION.prefix(GeneralStatementProvider.PARAM_PREFIX)))).build();
    }

    public GeneralStatementProvider deleteByPrimaryKey(BigInteger authorId, Long recordVersion) {
        String key = buildKey("deleteByPrimaryKey", BigInteger.class, Long.class);
        String deleteSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildDeleteByPrimaryKey(), DUMMY_INSTANCE));
        return GeneralStatementProviderImpl.builder(deleteSql).addParam("authorId", argNotNull(authorId, "authorId")).addParam("recordVersion", argNotNull(recordVersion, "recordVersion")).build();
    }

    protected Update buildUpdateByPrimaryKey(AuthorUpdate authorUpdate) {
        return update(AUTHOR)
            .set(c -> consumeIfTrue(authorUpdate::isAuthorIdUpdated, () -> c.set(AUTHOR_ID, PARAM_AUTHOR_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isAuthorNameUpdated, () -> c.set(AUTHOR_NAME, PARAM_AUTHOR_NAME.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isNationalityUpdated, () -> c.set(NATIONALITY, PARAM_NATIONALITY.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isContactUpdated, () -> c.set(CONTACT, PARAM_CONTACT.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isCreateDateTimeUpdated, () -> c.set(CREATE_DATE_TIME, PARAM_CREATE_DATE_TIME.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isUpdateDateTimeUpdated, () -> c.set(UPDATE_DATE_TIME, PARAM_UPDATE_DATE_TIME.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isDeleteDateTimeUpdated, () -> c.set(DELETE_DATE_TIME, PARAM_DELETE_DATE_TIME.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isCreateUserIdUpdated, () -> c.set(CREATE_USER_ID, PARAM_CREATE_USER_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isUpdateUserIdUpdated, () -> c.set(UPDATE_USER_ID, PARAM_UPDATE_USER_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isDeleteUserIdUpdated, () -> c.set(DELETE_USER_ID, PARAM_DELETE_USER_ID.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .set(c -> consumeIfTrue(authorUpdate::isRecordVersionUpdated, () -> c.set(RECORD_VERSION, PARAM_RECORD_VERSION.prefix(UpdateStatementProvider.UPDATE_PARAM_PREFIX))))
            .where(AUTHOR_ID.eq(PARAM_AUTHOR_ID.prefix(UpdateStatementProvider.UPDATE_CONDITION_PREFIX)), and(RECORD_VERSION.eq(PARAM_RECORD_VERSION.prefix(UpdateStatementProvider.UPDATE_CONDITION_PREFIX))))
            .build();
    }

    public UpdateStatementProvider updateByPrimaryKey(AuthorUpdate authorUpdate, BigInteger authorId, Long recordVersion) {
        String updateSql = sqlBuilder.build(buildUpdateByPrimaryKey(authorUpdate), DUMMY_INSTANCE);
        return UpdateStatementProviderImpl.builder(updateSql, authorUpdate).addParam("authorId", argNotNull(authorId, "authorId")).addParam("recordVersion", argNotNull(recordVersion, "recordVersion")).build();
    }

    protected Select buildSelectByPrimaryKey() {
        return select(COLUMNS).from(AUTHOR).where(AUTHOR_ID.eq(PARAM_AUTHOR_ID.prefix(GeneralStatementProvider.PARAM_PREFIX))).build();
    }

    public GeneralStatementProvider selectByPrimaryKey(BigInteger authorId) {
        String key = buildKey("selectByPrimaryKey", BigInteger.class);
        String selectSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildSelectByPrimaryKey(), DUMMY_INSTANCE));
        return GeneralStatementProviderImpl.builder(selectSql).addParam("authorId", argNotNull(authorId, "authorId")).build();
    }

    protected Select buildCuntByPrimaryKey() {
        return selectCount().from(AUTHOR).where(AUTHOR_ID.eq(PARAM_AUTHOR_ID.prefix(GeneralStatementProvider.PARAM_PREFIX))).build();
    }

    public GeneralStatementProvider countByPrimaryKey(BigInteger authorId) {
        String key = buildKey("countByPrimaryKey", BigInteger.class);
        String selectSql = sqlCache.computeIfAbsent(key, k -> sqlBuilder.build(buildCuntByPrimaryKey(), DUMMY_INSTANCE));
        return GeneralStatementProviderImpl.builder(selectSql).addParam("authorId", argNotNull(authorId, "authorId")).build();
    }

    public static Select buildSelectOrCountDynamic(QueryWrapper queryWrapper) {
        return select(queryWrapper.isCount(), queryWrapper.isDistinct(), ObjectHelper.isEmpty(queryWrapper.getColumns()) ? COLUMNS : queryWrapper.getColumns())
            .from(AUTHOR)
            .where(queryWrapper.getCondition())
            .orderBy(ObjectHelper.isNotEmpty(queryWrapper.getOrderColumns()), queryWrapper.getOrderColumns())
            .limit(queryWrapper.getLimit() != null && !queryWrapper.isCount(), queryWrapper.getLimit())
            .offset(queryWrapper.getOffset() != null && !queryWrapper.isCount(), queryWrapper.getOffset())
            .forUpdate(queryWrapper.isForUpdate() && !queryWrapper.isCount())
            .build();
    }

    public GeneralStatementProvider selectOrCountDynamic(QueryWrapper queryWrapper) {
        return generalSelect(buildSelectOrCountDynamic(queryWrapper));
    }

    public static Delete buildDeleteDynamic(Condition condition) {
        return deleteFrom(AUTHOR).where(condition).build();
    }

    public GeneralStatementProvider deleteDynamic(Condition condition) {
        return generalDelete(buildDeleteDynamic(condition));
    }

    public static Update buildUpdateDynamic(AuthorColumnUpdate authorColumnUpdate, Condition condition) {
        return update(AUTHOR)
            .set(c -> consumeIfTrue(authorColumnUpdate::isAuthorIdUpdated, () -> c.set(AUTHOR_ID, authorColumnUpdate.getAuthorId())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isAuthorNameUpdated, () -> c.set(AUTHOR_NAME, authorColumnUpdate.getAuthorName())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isNationalityUpdated, () -> c.set(NATIONALITY, authorColumnUpdate.getNationality())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isContactUpdated, () -> c.set(CONTACT, authorColumnUpdate.getContact())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isCreateDateTimeUpdated, () -> c.set(CREATE_DATE_TIME, authorColumnUpdate.getCreateDateTime())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isUpdateDateTimeUpdated, () -> c.set(UPDATE_DATE_TIME, authorColumnUpdate.getUpdateDateTime())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isDeleteDateTimeUpdated, () -> c.set(DELETE_DATE_TIME, authorColumnUpdate.getDeleteDateTime())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isCreateUserIdUpdated, () -> c.set(CREATE_USER_ID, authorColumnUpdate.getCreateUserId())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isUpdateUserIdUpdated, () -> c.set(UPDATE_USER_ID, authorColumnUpdate.getUpdateUserId())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isDeleteUserIdUpdated, () -> c.set(DELETE_USER_ID, authorColumnUpdate.getDeleteUserId())))
            .set(c -> consumeIfTrue(authorColumnUpdate::isRecordVersionUpdated, () -> c.set(RECORD_VERSION, authorColumnUpdate.getRecordVersion())))
            .where(condition)
            .build();
    }

    public GeneralStatementProvider updateDynamic(AuthorColumnUpdate authorColumnUpdate, Condition condition) {
        return generalUpdate(buildUpdateDynamic(authorColumnUpdate, condition));
    }
}
